This is a beginner-friendly guide to the official Django Rest Framework tutorial that works for both Windows and macOS. If you have struggled to complete the official tutorial on your own, consider this guide a good place to start instead.
The final code is exactly the same and is available on GitHub. However I provide more detailed explanations of each step, use the Django admin rather than the Django shell for populating the models, emphasize class-based views over function-based views, and so on.
After you complete this tutorial, the official tutorial should make more sense and help you fully take advantage of the awesomeness that is the Django REST Framework.
NOTE: If you'd like to learn even more about APIs in Django, I've written an entire book on the subject, Django for APIs. The first few chapters are available for free.
Initial Set UpYou will need to have the latest version of Python installed on your computer. If you need help with this, go to Chapter 1: Set Up of the Django for APIs book for a step-by-step guide to configuring your computer for Django development.
Once that's done, create a dedicated directory for our code and set up a new Python virtual environment. This can live anywhere on your computer but in an easily accessible location. One option is the Desktop, whether you are on a Windows or Mac computer. That's what we'll do here.
Enter the following commands in your terminal:
# Windows$ cd onedrive\desktop\code$ mkdir drf$ cd drf$ python -m venv .venv$ .venv\Scripts\Activate.ps1(.venv) $ # macOS$ cd desktop/desktop/code$ mkdir drf$ cd drf$ python3 -m venv .venv$ source .venv/bin/activate(.venv) $Then, within the new directory, install django, djangorestframework, and pygments (which is used for code highlighting). Once all three packages are installed, make sure to activate the virtual environment shell.
(.venv) $ python -m pip install django~=5.0.0(.venv) $ python -m pip install djangorestframework==3.15.2(.venv) $ python -m pip install pygments==2.18.0If you use the command pip freeze to output the virtual environment, you should see the following:
asgiref==3.8.1black==22.12.0click==8.1.3Django==5.0.7djangorestframework==3.15.2mypy-extensions==0.4.3pathspec==0.10.3platformdirs==2.6.2Pygments==2.18.0pytz==2022.7.1sqlparse==0.4.3Our new project is called tutorial, and within it, there is an app called snippets for our web API. Adding a period . at the end of the command is optional but recommended as otherwise Django will create an additional directory with the startproject command.
(.venv) $ django-admin startproject tutorial .(.venv) $ python manage.py startapp snippetsNow add the snippets app and rest_framework to the INSTALLED_APPS config in our tutorial/settings.py file. It's not enough to simply create new apps; they must also be explicitly added to INSTALLED_APPS.
# tutorial/settings.pyINSTALLED_APPS = ["django.contrib.admin","django.contrib.auth","django.contrib.contenttypes","django.contrib.sessions","django.contrib.messages","django.contrib.staticfiles","rest_framework", # new"snippets", # new]ModelsThe model is a good place to start any new project. In the snippets/models.py file, create a new model called Snippet.
# snippets/models.pyfrom django.db import modelsfrom pygments.lexers import get_all_lexersfrom pygments.styles import get_all_stylesLEXERS = [item for item in get_all_lexers() if item[1]]LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])class Snippet(models.Model):created = models.DateTimeField(auto_now_add=True)title = models.CharField(max_length=100, blank=True, default="")code = models.TextField()linenos = models.BooleanField(default=False)language = models.CharField(choices=LANGUAGE_CHOICES, default="python", max_length=100)style = models.CharField(choices=STYLE_CHOICES, default="friendly", max_length=100)class Meta:ordering = ["created"]def __str__(self):return self.titleThen create an initial migration file and sync the database for the first time.
(.venv) $ python manage.py makemigrations snippets(.venv) $ python manage.py migrateWe need to add some data to our model to make it "real" now. At this point, the official tutorial goes on a lengthy tangent into the Django shell. However, to beginners鈥攁nd even professional programmers鈥攖he graphical Django admin is often a more intuitive approach. That's what we'll use here.
But first, we need to update snippets/admin.py so the app will actually appear! Just as with the INSTALLED_APPS setting, apps must be explicitly added to the admin.
# snippets/admin.pyfrom django.contrib import adminfrom .models import Snippetadmin.site.register(Snippet)Now, create a superuser account to log in. Follow the prompts for setting a username, email, and password. I've used admin, ' [email protected], andtestpass123`.
(.venv) $ python manage.py createsuperuserAnd start our local web server for the first time.
(.venv) $ python manage.py runserverNavigate over to the Django homepage at http://127.0.0.1:8000/ to confirm everything is working.
Then switch over to the Django admin at http://127.0.0.1:8000/admin/. Log in with your superuser account.
Click on the "+ Add" button next to Snippets. And create two new snippets.
Click the "Save" button in the lower right for each snippet. Both will be visible on the main Snippets page.
SerializationA Serializer transforms model instances into JSON. This is the real "magic" that Django Rest Framework provides for us. Consider that the end result of a traditional website is a page of HTML, CSS, and content from the database. But an API doesn't care about that: it's only raw data at endpoints, which means JSON and accompanying HTTP verbs that tell the API what actions can be taken (more on this shortly).
Create a new snippets/serializers.py file and update it as follows. We can extend DRF's ModelSerializer to create a SnippetSerializer class that uses our model and outputs the table fields.
# snippets/serializersfrom rest_framework import serializersfrom .models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICESclass SnippetSerializer(serializers.ModelSerializer):class Meta:model = Snippetfields = ("id","title","code","linenos","language","style",)Next, we need a view that handles the logic of combining a model, serializer, and, eventually, URL together. Just as traditional Django ships with several class-based generic views to handle common functionality, so too does Django Rest Framework have its own set of powerful class-based generic views we can use.
Specifically we will use ListCreateAPIView to create a read-write endpoint that lists all available Snippet instances and then RetrieveUpdateDestroyAPIView for a read-write-delete endpoint for each individual Snippet.
# snippets/views.pyfrom rest_framework import genericsfrom .models import Snippetfrom .serializers import SnippetSerializerclass SnippetList(generics.ListCreateAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerclass SnippetDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerURLsThe final step is to configure our URLs. In the topmost, project-level tutorial/urls.py file, add include as an import for the snippets app URLs which will appear at the empty string ''.
# tutorial/urls.pyfrom django.contrib import adminfrom django.urls import include, path # newurlpatterns = [path("admin/", admin.site.urls),path("", include("snippets.urls")), # new]Then create a snippets/urls.py file and add the following code.
# snippets/urls.pyfrom django.urls import pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom snippets import viewsurlpatterns = [path("snippets/", views.SnippetList.as_view()),path("snippets//", views.SnippetDetail.as_view()),]urlpatterns = format_suffix_patterns(urlpatterns)Including format_suffix_patterns is an optional choice that provides a simple, DRY way to refer to a specific file format for a URL endpoint. It means our API will be able to handle URls such as http://example.com/api/items/4.json rather than just http://example.com/api/items/4.
Browsable APIDjango Rest Framework ships with a browsable API that we can now use. Make sure the local server is running.
(.venv) $ python manage.py runserverNavigate to the Snippets List endpoint at http://127.0.0.1:8000/snippets/.
We can also go to the detail view for each snippet. For example, the first snippet is at http://127.0.0.1:8000/snippets/1/.
As a reminder, the id is automatically set by Django on each database entry.
Requests and ResponsesCurrently our API has no restrictions on who can edit or delete code snippets. In this section we will make sure that:
Code snippets are always associated with a creatorOnly authenticated users may create snippetsOnly the creator of a snippet may update or delete itUnauthenticated requests should have full read-only accessAdding Information to Our ModelFirst up let's add two fields to our existing Snippet model class: owner which will represent the user who created the code snippet and highlighted to store the highlighted HTML representation of the code.
We also want to ensure that when the model is saved, we use the pygments code highlighting library to populate our highlighted field. So we'll need some additional imports as well as a .save() method.
# snippets/models.pyfrom django.db import modelsfrom pygments import highlight # newfrom pygments.formatters.html import HtmlFormatter # newfrom pygments.lexers import get_all_lexers, get_lexer_by_name # newfrom pygments.styles import get_all_stylesLEXERS = [item for item in get_all_lexers() if item[1]]LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])STYLE_CHOICES = sorted((item, item) for item in get_all_styles())class Snippet(models.Model):created = models.DateTimeField(auto_now_add=True)title = models.CharField(max_length=100, blank=True, default="")code = models.TextField()linenos = models.BooleanField(default=False)language = models.CharField(choices=LANGUAGE_CHOICES, default="python", max_length=100)style = models.CharField(choices=STYLE_CHOICES, default="friendly", max_length=100)owner = models.ForeignKey("auth.User", related_name="snippets", on_delete=models.CASCADE) # newhighlighted = models.TextField() # newclass Meta:ordering = ("created",)def save(self, *args, **kwargs): # new"""Use the `pygments` library to create a highlighted HTMLrepresentation of the code snippet."""lexer = get_lexer_by_name(self.language)linenos = "table" if self.linenos else Falseoptions = {"title": self.title} if self.title else {}formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options)self.highlighted = highlight(self.code, lexer, formatter)super(Snippet, self).save(*args, **kwargs)def __str__(self):return self.titleNormally we would create a migration and sync it to update our database tables. However since we have added an owner here and have existing content, it's simpler to just delete the database and start again. Make sure you have stopped the local server with Control+c.
(.venv) $ rm db.sqlite3(.venv) $ rm -r snippets/migrations(.venv) $ python manage.py makemigrations snippets(.venv) $ python manage.py migrateRe-create our steps from earlier to create a new superuser account. We'll want a second superuser account which is simplest to setup from the command line too. So run createsuperuser twice. I've called my users admin and testuser. Then start up the local server.
(.venv) $ python manage.py createsuperuser(.venv) $ python manage.py createsuperuser(.venv) $ python manage.py runserverGo back into the Django admin at http://127.0.0.1:8000/admin/ and login with the admin account.
If you click on the Users link you will be redirected to the Users page which should show both users.
Once complete, you should see the two users on the Users page.
We need to recreate our snippets too since the initial database was just destroyed. Create a new snippet and specify the Owner as one of our users. I've chosen testuser here.
But there's a problem when we try to "Save".
We are getting a ValidationError here. In the official tutorial the Django shell is used to input data, but we are using the admin here. So the existing code doesn't work as is. Recall that the highlighted field is automatically set by our custom save() method on the model, but the admin doesn't know this. It expects us to enter in a value here. To solve the problem update our admin.py file and set highlighted as a read-only field.
# snippets/admin.pyfrom django.contrib import adminfrom .models import Snippetclass SnippetAdmin(admin.ModelAdmin):readonly_fields = ("highlighted",)admin.site.register(Snippet, SnippetAdmin)Try clicking the "Save" button again. It should work.
The final step is to click the Log out link in the upper right corner of the admin page.
We will shortly be adding permissions to our API so that only authenticated (logged-in) users have access.
Adding Endpoints to Our User ModelsNow that we have some users to work with, let's add endpoints for them to our API. Add a new UserSerializer class to the snippets/serializers.py file.
# snippets/serializers.pyfrom django.contrib.auth.models import Userfrom rest_framework import serializersfrom snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICESclass SnippetSerializer(serializers.ModelSerializer):class Meta:model = Snippetfields = ("id","title","code","linenos","language","style",)class UserSerializer(serializers.ModelSerializer):snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())class Meta:model = Userfields = ("id", "username", "snippets")Because snippets is a reverse relationship on the default Django User model, it will not be included by default using the ModelSerializer class, we need to add an explicit field for it.
We also need to add two new read-only views for a list of all users and a detail view of individual users. Note that we use the generic class-based RetrieveAPIView for the read-only detail view. And that we import both User and UserSerializer at the top.
# snippets/views.pyfrom django.contrib.auth.models import User # newfrom rest_framework import genericsfrom .models import Snippetfrom .serializers import SnippetSerializer, UserSerializer # newclass SnippetList(generics.ListCreateAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerclass SnippetDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerclass UserList(generics.ListAPIView): # newqueryset = User.objects.all()serializer_class = UserSerializerclass UserDetail(generics.RetrieveAPIView): # newqueryset = User.objects.all()serializer_class = UserSerializerFinally we need to add the new views to the API by configuring their URL routes. Add the following pattern to snippets/urls.py.
# snippets/urls.pyfrom django.urls import pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom snippets import viewsurlpatterns = [path("snippets/", views.SnippetList.as_view()),path("snippets//", views.SnippetDetail.as_view()),path("users/", views.UserList.as_view()), # newpath("users//", views.UserDetail.as_view()), # new]urlpatterns = format_suffix_patterns(urlpatterns)Associating Snippets with UsersCurrently there is no way to automatically associate the logged-in user that created a snippet with the snippet instance. We can set this automatically by overriding .perform_create() method on our snippet views that lets us modify how an instance is saved.
Add the following method to our existing SnippetList view class.
# snippets/views.pyclass SnippetList(generics.ListCreateAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerdef perform_create(self, serializer): # newserializer.save(owner=self.request.user)Updating Our SerializerNow that snippets are associated with the user that created them, let's update SnippetSerializer with an owner to reflect that. Make sure to also include owner in the list of fields too.
# snippets/serializers.pyfrom django.contrib.auth.models import Userfrom rest_framework import serializersfrom snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICESclass SnippetSerializer(serializers.ModelSerializer):owner = serializers.ReadOnlyField(source="owner.username") # newclass Meta:model = Snippetfields = ("id","title","code","linenos","language","style","owner",) # newclass UserSerializer(serializers.ModelSerializer):snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())class Meta:model = Userfields = ("id", "username", "snippets")The source argument used here controls which attribute is used to populate a field and can point to any attribute on the serialized instance. Also note that we're using ReadOnlyField which is always read-only; it can not be used for updating model instances when they are serialized. We could have also used CharField(read_only=True) here to accomplish the same thing.
Adding Required Permissions To ViewsNow that code snippets are associated with users, we want to make sure that only authenticated users are able to create, update, and delete code snippets.
Django Rest Framework ships with a number of permission classes we could use to restrict access to a given view. Here we will use IsAuthenticatedOrReadOnly to ensure that authenticated requests have read-write access and unauthenticated requests only have read-only access.
# snippets/views.pyfrom django.contrib.auth.models import Userfrom rest_framework import generics, permissions # newfrom .models import Snippetfrom .serializers import SnippetSerializer, UserSerializerclass SnippetList(generics.ListCreateAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerpermission_classes = (permissions.IsAuthenticatedOrReadOnly,) # newdef perform_create(self, serializer):serializer.save(owner=self.request.user)class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerpermission_classes = (permissions.IsAuthenticatedOrReadOnly,) # newclass UserList(generics.ListAPIView):queryset = User.objects.all()serializer_class = UserSerializerclass UserDetail(generics.RetrieveAPIView):queryset = User.objects.all()serializer_class = UserSerializerAdding Log In To The Browsable APINow navigate to our browsable API at http://127.0.0.1:8000/snippets/.
Since we are logged out, notice that you are no longer able to create new code snippets. In order to do so you need to be logged in as a user.
We can add a login view to the browsable API by editing the URLconf in our project-level tutorial/urls.py file. Add rest_framework.urls to the route api-auth/.
# tutorial/urls.pyfrom django.contrib import adminfrom django.urls import include, pathurlpatterns = [path("admin/", admin.site.urls),path("api-auth/", include("rest_framework.urls")), # newpath("", include("snippets.urls")),]Note that the actual route used does not matter. Instead of api-auth/ we could also have used something-else/. The important thing is that rest_framework.urls was included.
Now open up the browser again and refresh the page. You will see a Log in link in the top right of the page.
Log in with your testuser account. Then navigate to our http://127.0.0.1:8000/users/ endpoint and notice that snipped ids are associated with each user, as desired.
We only have one snippet, made with our testuser account and containing the primary id of 1. If we added additional snippets for each user, they'd appear here as well. So things are working.
Object Level PermissionsReally what we'd like is for all code snippets to be visible to anyone, but only the user that created a code snippet can update or delete it.
Django Rest Framework gives us several options for setting permissions: at a project-level, view level, or object level. In this case we will implement the last option and create a custom permission we can add to our SnippetDetail view class.
Create a new snippets/permissions.py file and then add the following code which extends Django Rest Framework's existing permissions classes.
# snippets/permissions.pyfrom rest_framework import permissionsclass IsOwnerOrReadOnly(permissions.BasePermission):"""Custom permission to only allow owners of an object to edit it."""def has_object_permission(self, request, view, obj):# Read permissions are allowed to any request,# so we'll always allow GET, HEAD or OPTIONS requests.if request.method in permissions.SAFE_METHODS:return True# Write permissions are only allowed to the owner of the snippet.return obj.owner == request.userNext add the new custom permission to SnippetDetail by importing it at the top and including it in permission_classes.
# snippets/views.pyfrom django.contrib.auth.models import Userfrom rest_framework import generics, permissionsfrom .models import Snippetfrom .permissions import IsOwnerOrReadOnly # newfrom .serializers import SnippetSerializer, UserSerializerclass SnippetList(generics.ListCreateAPIView):...class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Snippet.objects.all()serializer_class = SnippetSerializerpermission_classes = (permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly,) # new...If you open the browser again to http://127.0.0.1:8000/snippets/1/ you will find that the 'DELETE' and 'PUT' actions appear on the snippet instance endpoint because we're logged in as testuser, the owner of the snippet.
Now log out and log in with the admin account. The DELETE and PUT options are not available. Good, as expected.
Root API EndpointCurrently there are endpoints for snippets and users, but we don't have a single entry point to our API. To create one, we'll use a regular function-based view and Django REST Framework's built-in @api_view decorator.
In snippets/views.py import api_view, Response, and reverse. Then use @api_view to set a GET for api_root.
# snippets/views.pyfrom django.contrib.auth.models import Userfrom rest_framework import generics, permissionsfrom rest_framework.decorators import api_view # newfrom rest_framework.response import Response # newfrom rest_framework.reverse import reverse # newfrom .models import Snippetfrom .permissions import IsOwnerOrReadOnlyfrom .serializers import SnippetSerializer, UserSerializer@api_view(["GET"]) # newdef api_root(request, format=None):return Response({"users": reverse("user-list", request=request, format=format),"snippets": reverse("snippet-list", request=request, format=format),})class SnippetList(generics.ListCreateAPIView):......Next we need to add a URL at the empty string "" for api_root. And since we're using reverse we also must add named urls to each existing view.
# snippets/urls.pyfrom django.urls import pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom snippets import viewsurlpatterns = [path("snippets/", views.SnippetList.as_view(), name="snippet-list"),path("snippets//", views.SnippetDetail.as_view(), name="snippet-detail"),path("users/", views.UserList.as_view(), name="user-list"),path("users//", views.UserDetail.as_view(), name="user-detail"),path("", views.api_root),]urlpatterns = format_suffix_patterns(urlpatterns)Now navigate to http://127.0.0.1:8000/ to see our new API Root page.
It lists both users and snippets as well as their respective API endpoints which can be clicked on.
Highlighted Snippets EndpointThe other obvious thing that's still missing from our pastebin API is the code highlighting endpoints.
Unlike all our other API endpoints, we don't want to use JSON, but instead just present an HTML representation. REST framework has two HTML renderers: one for dealing with HTML rendered using templates and one for pre-rendered HTML (which is our case here).
Also, there's no existing generic view that will work so we'll need to create our own .get() method.
In your snippets/views.py import renderers at the top and then create a new class for SnippetHighlight.
# snippets/views.pyfrom django.contrib.auth.models import Userfrom rest_framework import generics, permissions, renderers # newfrom rest_framework.decorators import api_viewfrom rest_framework.response import Responsefrom rest_framework.reverse import reversefrom .models import Snippetfrom .permissions import IsOwnerOrReadOnlyfrom .serializers import SnippetSerializer, UserSerializerclass SnippetHighlight(generics.GenericAPIView): # newqueryset = Snippet.objects.all()renderer_classes = (renderers.StaticHTMLRenderer,)def get(self, request, *args, **kwargs):snippet = self.get_object()return Response(snippet.highlighted)@api_view(["GET"]) ...Add the new view to the urls file. Make sure to include the name snippet-highlight!
# snippets/urls.pyfrom django.urls import pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom snippets import viewsurlpatterns = [path("snippets/", views.SnippetList.as_view(), name="snippet-list"),path("snippets//", views.SnippetDetail.as_view(), name="snippet-detail"),path("snippets//highlight/",views.SnippetHighlight.as_view(),name="snippet-highlight",), # newpath("users/", views.UserList.as_view(), name="user-list"),path("users//", views.UserDetail.as_view(), name="user-detail"),path("", views.api_root),]urlpatterns = format_suffix_patterns(urlpatterns)We only have one snippet in our database so the highlight will be located at http://127.0.0.1:8000/snippets/1/highlight/.
Hyperlinking our APIOne of the more challenging aspects of web API design is dealing with the relationships between entities. We could use primary key, hyperlinks, slugs, strings, nesting, or a custom representation.
REST framework supports all of these styles but here we'll use a hyperlinked style between entities. In order to do so, we'll modify our serializers to extend HyperlinkedModelSerializer instead of the existing ModelSerializer.
The HyperlinkedModelSerializer has the following differences from ModelSerializer:
It does not include the id field by default.It includes a url field, using HyperlinkedIdentityField.Relationships use HyperlinkedRelatedField, instead of PrimaryKeyRelatedField.Let's rewrite our existing serializers to use hyperlinks.
# snippets/serializers.pyfrom django.contrib.auth.models import Userfrom rest_framework import serializersfrom snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICESclass SnippetSerializer(serializers.HyperlinkedModelSerializer): # newowner = serializers.ReadOnlyField(source="owner.username")highlight = serializers.HyperlinkedIdentityField( # newview_name="snippet-highlight", format="html")class Meta:model = Snippetfields = ("url","id","highlight","title","code","linenos","language","style","owner",) # newclass UserSerializer(serializers.HyperlinkedModelSerializer): # newsnippets = serializers.HyperlinkedRelatedField( # newmany=True, view_name="snippet-detail", read_only=True)class Meta:model = Userfields = ("url", "id", "username", "snippets") # newAside from swapping in HyperlinkedModelSerializer there is a new highlight field for snippets that points to the snippet-highlight url pattern, instead of the snippet-detail url pattern.
Also for the fields we add url to both and highlight to the snippet serializer.
PaginationCurrently we only have the one code snippet but as others are added it makes sense to limit the number of snippets displayed per API endpoint. Let's paginate the results so that API clients can step through each of the individual pages.
REST Framework ships with a number of default settings which can be easily customized. We'll set a DEFAULT_PAGINATION_CLASS and PAGE_SIZE to 10 although we could easily customize things further as desired.
At the bottom of the tutorial/settings.py file add the following:
# tutorial/settings.pyREST_FRAMEWORK = {"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination","PAGE_SIZE": 10,}At this point it's possible to click around the entire API just via links. Success!
Viewsets and RoutersSection 6 of the official tutorial has us switch over from views and URLs to viewsets and routers. This is an optional choice that is, in my opinion, better suited to larger API projects and for developers already comfortable with REST framework. Since neither applies here we will not update our code. The resulting API endpoints will still be exactly the same!
ConclusionWith an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, includes a schema-driven client library, and comes complete with authentication, per-object permissions, and multiple renderer formats.
We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
You can review the final tutorial code on GitHub.
And if you'd like to learn even more about APIs in Django, check out my book Django for APIs.